﻿// IPC Library
// Interprocess Communication Library
// Copyright(C)2008 Bill Tudor
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using System.Threading;
using System.Globalization;
using Microsoft.Win32.SafeHandles;

namespace IPCLibrary
{
	/// <summary>
	/// Memory Mapped File.
	/// </summary>
	/// <remarks>
	/// An instance of shared memory backed by memory mapped file.
	/// </remarks>
	public class MemoryMappedFile : IDisposable
	{
		private SafeViewOfFileHandle _baseAddress;
		private SafeFileHandle _fileMapping;
		private SafeHandle _fileHandle;
		private int _size;
		private string _name;
		private bool _disposed;
		private Mutex _mutex;

		/// <summary>
		/// Mapping name.
		/// </summary>
		public String Name
		{
			get { return _name; }
		}
		/// <summary>
		/// Size in bytes.
		/// </summary>
		public int Size
		{
			get { return _size; }
		}
		/// <summary>
		/// Create a new memory mapped file.
		/// </summary>
		/// <param name="size">Size of memory in bytes.</param>
		/// <remarks>
		/// This method will create a un-named memory mapping backed by the system paging file.
		/// </remarks>
		public MemoryMappedFile(int size)
			: this(size, null) { }
		/// <summary>
		/// Create a new memory mapped file.
		/// </summary>
		/// <param name="size">Size of the memory in bytes.</param>
		/// <param name="name">Name of the file mapping object.</param>
		/// <remarks>
		/// This method will create a named memory mapping backed by the system paging file.
		/// </remarks>
		public MemoryMappedFile(int size, string name)
		{
			CreateMemoryMappedFile(size, name, MemoryMappedFileSafeHandle.Zero());
		}
		/// <summary>
		/// Create a new memory mapped file.
		/// </summary>
		/// <param name="fileName">Pathname of the file to use for mapping.</param>
		/// <remarks>
		/// This method will create an un-named memory mapping backed by the specified file.
		/// </remarks>
		public MemoryMappedFile(string fileName)
			:this(fileName, null) {}
		/// <summary>
		/// Create a new memory mapped file.
		/// </summary>
		/// <param name="fileName">Pathname of the file to use for mapping.</param>
		/// <param name="name">Name of the file mapping object.</param>
		public MemoryMappedFile(string fileName, string name)
		{
			SafeFileHandle fileHandle = NativeMethods.CreateFile(fileName, NativeMethods.FILE_READ_DATA | NativeMethods.FILE_WRITE_DATA, NativeMethods.FILE_SHARE_READ | NativeMethods.FILE_SHARE_WRITE, IntPtr.Zero, NativeMethods.OPEN_EXISTING, NativeMethods.FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
			if (fileHandle.IsInvalid)
			{
				throw IPCException.FromLastError("Unable to open the mapping file.", "OpenFile");
			}
			Int64 size = 0;
			if (!NativeMethods.GetFileSizeEx(fileHandle, out size))
			{
				fileHandle.Close();
				throw IPCException.FromLastError("Unable to determine the file size.", "GetFileSizeEx");
			}
			if (size > Int32.MaxValue)
			{
				throw new IPCException("Mapping file is too large.");
			}
			CreateMemoryMappedFile((int)size, name, fileHandle);
		}
		/// <summary>
		/// Finalizer.
		/// </summary>
		~MemoryMappedFile() { this.Dispose(false); }

		private void CreateMemoryMappedFile(int size, string name, SafeHandle fileHandle)
		{
			_size = size;
			_name = name;
			_fileHandle = fileHandle;
			_fileMapping = NativeMethods.CreateFileMapping(fileHandle, IntPtr.Zero, NativeMethods.PAGE_READWRITE, 0, size, name);
			if (_fileMapping == null || _fileMapping.IsInvalid)
			{
				throw IPCException.FromLastError("Unable to create file mapping kernel object.", "CreateFileMapping");
			}
			
			_baseAddress = NativeMethods.MapViewOfFile(_fileMapping, NativeMethods.FILE_MAP_READ | NativeMethods.FILE_MAP_WRITE, 0, 0, size);
			if (_baseAddress == null || _baseAddress.IsInvalid)
			{
				throw IPCException.FromLastError("Unable to map view of file.", "MapViewOfFile");
			}
		}

		#region IDisposable Members
		/// <summary>
		/// Dispose of unmanged resources.
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		/// <summary>
		/// Dispose of the object.
		/// </summary>
		/// <param name="disposing">True if disposing.</param>
		protected virtual void Dispose(bool disposing)
		{
			if (!_disposed)
			{
				if (_mutex != null)
				{
					_mutex.Close();
				}
				if (_baseAddress != null && !_baseAddress.IsInvalid)
				{
					_baseAddress.Dispose();
				}
				if (_fileMapping != null && !_fileMapping.IsInvalid)
				{
					_fileMapping.Close();
					_fileMapping.Dispose();
				}
				if (_fileHandle != null && !_fileHandle.IsInvalid)
				{
					_fileHandle.Close();
					_fileHandle.Dispose();
				}
				_disposed = true;
			}
		}

		#endregion

		/// <summary>
		/// Read bytes from shared memory.
		/// </summary>
		/// <param name="offset">Zero-based offset from the start of shared memory.</param>
		/// <param name="count">Number of bytes to read.</param>
		/// <returns>Bytes from shared memory.</returns>
		public byte[] ReadBytes(int offset, int count)
		{
			byte[] result = new byte[count];
			ReadBytes(result, 0, offset, count);
			return result;
		}
		/// <summary>
		/// Read bytes from shared memory.
		/// </summary>
		/// <param name="buffer">Buffer to receive the bytes.</param>
		/// <param name="dataOffset">Offset into shared memory to start reading.</param>
		/// <param name="count">Number of bytes to read.</param>
		/// <returns>Number of bytes read.</returns>
		public int ReadBytes(byte[] buffer, int dataOffset, int count)
		{
			return ReadBytes(buffer, 0, dataOffset, count);
		}
		/// <summary>
		/// Read bytes from shared memory.
		/// </summary>
		/// <param name="buffer">Buffer to receive the bytes.</param>
		/// <param name="bufferOffset">Offset in the receive buffer to copy to.</param>
		/// <param name="dataOffset">Offset into shared memory to start reading.</param>
		/// <param name="count">Number of bytes to read.</param>
		/// <returns>Number of bytes read.</returns>
		public int ReadBytes(byte[] buffer, int bufferOffset, int dataOffset, int count)
		{
			return ReadBytes(buffer, bufferOffset, dataOffset, count, false);
		}
		/// <summary>
		/// Read bytes from shared memory.
		/// </summary>
		/// <param name="buffer">Buffer to receive the bytes.</param>
		/// <param name="bufferOffset">Offset in the receive buffer to copy to.</param>
		/// <param name="dataOffset">Offset into shared memory to start reading.</param>
		/// <param name="count">Number of bytes to read.</param>
		/// <returns>Number of bytes read.</returns>
		/// <remarks>
		/// The read operation is synchronized around a named mutex.
		/// </remarks>
		public int SynchronizedRead(byte[] buffer, int bufferOffset, int dataOffset, int count)
		{
			return ReadBytes(buffer, bufferOffset, dataOffset, count, true);
		}
		/// <summary>
		/// Write bytes to shared memory.
		/// </summary>
		/// <param name="data">Bytes to write.</param>
		/// <param name="offset">Zero-based offset from the start of shared memory.</param>
		/// <param name="count">Number of bytes to write.</param>
		public void WriteBytes(byte[] data, int offset, int count)
		{
			WriteBytes(data, 0, offset, count, false);
		}
		/// <summary>
		/// Write bytes to shared memory.
		/// </summary>
		/// <param name="data">Bytes to write.</param>
		/// <param name="offset">Zero-based offset from the start of shared memory.</param>
		/// <param name="count">Number of bytes to write.</param>
		/// <remarks>
		/// The write is synchronized around a named mutex.
		/// </remarks>
		public void SynchronizedWrite(byte[] data, int offset, int count)
		{
			WriteBytes(data, 0, offset, count, true);
		}
		/// <summary>
		/// Return a stream that can be used to read and write data.
		/// </summary>
		/// <returns>Stream.</returns>
		public Stream GetStream()
		{
			return new MemoryMappedFileStream(this);
		}

		private void ValidateSize(int offset, int count)
		{
			if (offset + count > _size)
			{
				throw new ArgumentException("Attempt to read/write beyond the size of shared memory.");
			}
		}

		internal void WriteBytes(byte[] buffer, int bufferOffset, int offset, int count, bool synchronized)
		{
			ValidateSize(offset, count);
			if (synchronized)
			{
				GrabMutex();
				Marshal.Copy(buffer, bufferOffset, new IntPtr(_baseAddress.ToInt32() + offset), count);
				ReleaseMutex();
			}
			else
			{
				Marshal.Copy(buffer, bufferOffset, new IntPtr(_baseAddress.ToInt32() + offset), count);
			}
		}
		private int ReadBytes(byte[] buffer, int bufferOffset, int dataOffset, int count, bool syncrhonized)
		{
			ValidateSize(dataOffset, count);
			if (syncrhonized)
			{
				GrabMutex();
				Marshal.Copy(new IntPtr(_baseAddress.ToInt32() + dataOffset), buffer, bufferOffset, count);
				ReleaseMutex();
			}
			else
			{
				Marshal.Copy(new IntPtr(_baseAddress.ToInt32() + dataOffset), buffer, bufferOffset, count);
			}
			return count;
		}
		private void GrabMutex()
		{
			CreateMutex();
			_mutex.WaitOne();
		}
		private void CreateMutex()
		{
			if (_mutex == null)
			{
				lock (this)
				{
					if (_mutex == null)
					{
						_mutex = new Mutex(false, String.Format(CultureInfo.CurrentCulture, "{0}_mutex", _name));
					}
				}
			}
		}
		private void ReleaseMutex()
		{
			_mutex.ReleaseMutex();
		}
	}
}
